/**
 * 
 */
package ru.olympus.antonyhands.ao.server.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;

/**
 * @author AntonyHands
 * @since 14.02.2012
 */
public class GoogleIterable<T> implements Iterable<T> {

    private Iterable<T> _iterable;

    // TODO добавить обертки для остальных методов

    private GoogleIterable(final T[] array)
    {
        _iterable = Arrays.asList(array);
    }

    private GoogleIterable(final Iterable<T> iterable)
    {
        _iterable = iterable;
    }

    /**
     * Присоединяемся к набору данных
     *
     * @param <T>
     * @param iterable
     * @return
     */
    public static <T> GoogleIterable<T> with(final Iterable<T> iterable)
    {
        return new GoogleIterable<T>(iterable);
    }

    /**
     * Присоединяемся к набору данных
     *
     * @param <T>
     * @param iterable
     * @return
     */
    public static <T> GoogleIterable<T> with(final T[] array)
    {
        return array == null ? new GoogleIterable<T>(Collections.<T>emptyList()) : with(Arrays.asList(array));
    }

    /**
     * Возвращаем объекты из нашего набора данных, удовлетворяющие некоторому предикату
     * @param predicate
     * @return
     */
    public GoogleIterable<T> filter(final Predicate<? super T> predicate)
    {
        Iterable<T> filtered = Iterables.filter(_iterable, predicate);
        _iterable = filtered;
        return this;
    }

    /**
     * Возвращаем объекты из нашего набора данных, являющиеся объектом некоторого класса
     * @param type
     * @return
     */
    public GoogleIterable<T> filter(final Class<? extends T> type)
    {
        _iterable = Iterables.filter(_iterable, new Predicate<T>()
            {
                @Override
                public boolean apply(T input)
                {
                    return input.getClass().isAssignableFrom(type);
                }
            });
        return this;
    }

    /**
     * Возвращаем объекты из нашего набора данных, являющиеся объектом некоторого класса
     * в виде списка объектов указанного класса
     * 
     * @param type - класс, объекты которого желаем получить
     * @return
     */
    public <D extends T> GoogleIterable<D> filterAndTransform(final Class<D> type)
    {
       return filter(type).transform(new Function<T, D>()
       {
            @SuppressWarnings("unchecked")
            @Override
            public D apply(T from)
            {
                return (D)from;
            }
       });
    }

    /**
     * Преобразуем объекты из нашего набора данных с помощью функции <code>function</code>
     *
     * @param <D> destination
     * @param function
     * @return
     */
    public <D> GoogleIterable<D> transform(final Function<? super T, ? extends D> function)
    {
       return new GoogleIterable<D>(Iterables.transform(_iterable, function));
    }

    /**
     * Добавляем элемент в наш набор данных
     * @param element
     * @return
     */
    public GoogleIterable<T> add(final T element)
    {
        Collection<T> coll = new ArrayList<T>();;
        Iterables.addAll(coll, _iterable);
        coll.add(element);
        _iterable = coll;
        return this;
    }
    
    public GoogleIterable<T> addFirst(final T element)
    {
        Collection<T> coll = new ArrayList<T>();;
        coll.add(element);
        Iterables.addAll(coll, _iterable);
        _iterable = coll;
        return this;
    }

    /**
     * Добавляем элементы в наш набор данных
     * @param elements
     * @return
     */
    public GoogleIterable<T> addAll(final Iterable<T> elements)
    {
        Collection<T> coll = new ArrayList<T>();
        Iterables.addAll(coll, _iterable);
        Iterables.addAll(coll, elements);
        _iterable = coll;
        return this;
    }

    /**
     * Преобразуем набор данных в список и сортируем его с помощью компаратора
     * @param comparator
     * @return
     */
    public GoogleIterable<T> sort(final Comparator<T> comparator)
    {
        List<T> list = toArrayList();
        Collections.sort(list, comparator);
        _iterable = list;
        return this;
    }

    /**
     * "Спрямление" - преобразуем коллекцию коллекций в коллекцию, инкапсулирующую все
     * элементы этих коллекций.
     * @param <E>
     * @return
     */
    @SuppressWarnings("unchecked")
    public <E> GoogleIterable<E> flat()
    {
        Collection<E> coll = new ArrayList<E>();
        for (T element : _iterable)
            Iterables.addAll(coll, (Iterable<? extends E>) element);

        return new GoogleIterable<E>(coll);
    }

    /**
     * Очищаем коллекцию от нулевых элементов
     * @return
     */
    public GoogleIterable<T> trim()
    {
        return filter(new Predicate<T>()
        {
            @Override
            public boolean apply(T input)
            {
                return input != null;
            }
        });
    }

    @Override
    public Iterator<T> iterator()
    {
      return _iterable.iterator();
    }

    public T[] toArray(T[] array)
    {
        List<T> list = toArrayList();
        return list.toArray(array);
    }

    public List<T> toArrayList()
    {
        List<T> list = new ArrayList<T>();
        fillCollection(list);
        return list;
    }

    public List<T> toLinkedList()
    {
        List<T> list = new LinkedList<T>();
        fillCollection(list);
        return list;
    }

    public Set<T> toHashSet()
    {
        Set<T> set = new HashSet<T>();
        fillCollection(set);
        return set;
    }

    public T first()
    {
        if (iterator().hasNext())
            return iterator().next();
        else
            return null;
    }

    /**
     * Возвращаем первый элемент коллекции, удовлетворяющий условию, описанному
     * переданным предикатом
     *
     * @param predicate
     * @return
     */
    public T get(Predicate<T> predicate)
    {
        filter(predicate);
        return first();
    }
    
    /**
     * Заполняем переданную коллекцию значениями из итератора для объекта
     * @param collection
     */
    private void fillCollection(Collection<T> collection)
    {
        Iterators.addAll(collection, iterator());
    }
}